
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;

namespace Derm.Render
{
	/// <summary>
	/// Rendering kernel. 
	/// </summary>
	/// <remarks>
	/// <para>
	/// The RenderKernel class allows to manage rendering pipeline operations.
	/// </para>
	/// <b>Execution Model</b>
	/// <para>
	/// A RenderKernel instance allow to execute a set of pipelines. The pipelines are executed
	/// on a separate thread, started when <see cref="RenderKernel.RunKernels"/> is called from
	/// the application entry point. Being a separate thread, allow to execute different set
	/// of pipelines using different processing units (both CPU and GPU, if possible).
	/// </para>
	/// <para>
	/// All registered pipelines are executed sequentially. The execution order is determine by
	/// the registration order (the first registered is the first executed). Application determine
	/// pipeline dependencies by registering dependent pipelines after the ones without dependencies
	/// (i.e. a pipeline execution is dependent on the result of a previous pipeline).
	/// </para>
	/// <para>
	/// Before all pipelines are executed, there is an initialization (override <see cref="RenderPipeline.InitializaPipeline"/>).
	/// This initialization is execute once for all registered pipelines; if all pipeline initializations were successful, the
	/// render kernel loop can begin.
	/// </para>
	/// <para>
	/// The rendering loop consists of a repeatitive sequence of pipeline executions. The sequence can be defines as follow:
	/// - For each registered pipeline, if it is enabled:
	///		- Reset the pipeline (<see cref="RenderPipeline.ResetPipeline"/>)
	///		- Execute the pipeline (<see cref="RenderPipeline.ExecutePipeline"/>
	/// - Swap all surfaces (associated to enabled pipelines) that declares to be double bufferes
	/// - Wait all other running kernels to reach the same execution point
	/// </para>
	/// </remarks>
	public sealed class RenderKernel
	{
		#region Multithread Kernel

		/// <summary>
		/// Barrier used to synchronize multiple kernels.
		/// </summary>
		/// <remarks>
		/// Swappable surfaces shall be swapped after all kernels have rendered all frames. Then,
		/// it's possible to swap all registered surfaces.
		/// </remarks>
		private class KernelBarrier
		{
			/// <summary>
			/// Construct a KernelBarrier.
			/// </summary>
			/// <param name="kernels">
			/// A <see cref="System.UInt32"/> that specifies how many kernels are working on this
			/// KernelBarrier.
			/// </param>
			/// <exception cref="ArgumentException">
			/// Exception thrown if <paramref name="kernels"/> is 0.
			/// </exception>
			public KernelBarrier(uint kernels)
			{
				if (kernels == 0)
					throw new ArgumentException("invalid value 0", "kernels");
				// Store thread count.
				mThreadLoopCount = mKernelCount = kernels;
			}

			/// <summary>
			/// Number of kernels working on this KernelBarrier.
			/// </summary>
			public uint KernelCount
			{
				get {
					return (mKernelCount);
				}
			}

			/// <summary>
			/// Blocks current thread, untill every RenderKernel is waiting on this KernelBarrier.
			/// </summary>
			/// <param name="terminate">
			/// A <see cref="System.Boolean"/> that specifies whether the current thread is terminating.
			/// </param>
			public void WaitOtherKernels(bool terminate)
			{
				lock (this) {
					if ((mKernelCount == 1) || (--mThreadLoopCount == 0)) {
						// Release main thread
						mBarrierLock.Release();
						// Block all RenderKernel kernels untill main thread has finished
						mKernelLock.WaitOne();

						// Release this thread, release all RenderKernel kernels
						mThreadLoopCount = mKernelCount;
						/* Release all kernels */
						Monitor.PulseAll(this);
					} else {
						if (terminate)
							mKernelCount--;

						/* Block thread */
						Monitor.Wait(this);
					}
				}
			}

			/// <summary>
			/// Block current thread untill every RenderKernel is blocked on this KernelBarrier.
			/// </summary>
			public void WaitKernelBarrier()
			{
				mBarrierLock.WaitOne();
			}

			/// <summary>
			/// Release this KernelBarries, with also every RenderKernel blocked on this KernelBarrier.
			/// </summary>
			public void ReleaseKernelBarrier()
			{
				mKernelLock.Release();
			}

			/// <summary>
			/// RenderKernel kernels using this barrier.
			/// </summary>
			private uint mKernelCount;

			/// <summary>
			/// RenderKernel kernels not blocked on barrier.
			/// </summary>
			private uint mThreadLoopCount;

			/// <summary>
			/// Semaphore for blocking the main thread.
			/// </summary>
			private Semaphore mBarrierLock = new Semaphore(0, 1);

			/// <summary>
			/// Lock for blocking RenderKernel threads.
			/// </summary>
			private Semaphore mKernelLock = new Semaphore(0, 1);
		}

		/// <summary>
		/// Register a RenderKernel.
		/// </summary>
		/// <param name="kernel">
		/// 
		/// </param>
		public static void RegisterKernel(RenderKernel kernel)
		{
			sKernels.Add(kernel);
		}

		/// <summary>
		/// Register a RenderKernel.
		/// </summary>
		/// <param name="kernel">
		/// 
		/// </param>
		public static void UnregisterKernel(RenderKernel kernel)
		{
			sKernels.Remove(kernel);
		}

		/// <summary>
		/// RenderKernel run routine.
		/// </summary>
		public static void RunKernels()
		{
			if (sKernels.Count == 0)
				throw new InvalidOperationException("no kernel registered");

			// Create a thread for each RenderKernel detachable service
			foreach (RenderKernelService s in sKernelServices)
				if (s.IsMultithreadModel)
					s.Start();

			if (sKernels.Count > 1) {

				// Note: multiple kernels running on different threads

				// Create shader render context
				sSharedWindow.Create(new RenderSurfaceFormat(Pixel.Type.RGBA32));
				sSharedContext = new RenderContext(sSharedWindow.GetDeviceContext());

				// Make shared context current to this thread
				sSharedContext.MakeCurrent(sSharedWindow.GetDeviceContext(), true);

				// Create barrier (multiple kernels synchronization)
				sKernelBarrier = new KernelBarrier((uint)sKernels.Count);

				foreach (RenderKernel k in sKernels) {
					// Create rendering contexts (one for each kernel)
					k.CreateRenderContext();
					// Start render kernel kernels
					k.mThread = new Thread(RenderKernelMain);
					k.mThread.Start(k);
				}

				while (sKernelBarrier.KernelCount > 0) {
					// Wait untill all kernel loops has finished
					sKernelBarrier.WaitKernelBarrier();

					if (sPauseKernels == true) {
						// Block every running kernel
						while ((sPauseKernels == true) && (sQuitKernels == false)) {
							// Handle eventual forms not controlled by kernels
							Application.DoEvents();
							// Avoid CPU burning
							Thread.Sleep(1);
						}
					} else {
						// Do application events
						Application.DoEvents();
					}

					// Forced termination
					if (sQuitKernels == true) break;

					// Execute shared context services
					foreach (RenderKernelService s in sKernelServices)
						if (s.RequiresKernelContext)
							s.ContextExecute(sSharedContext);

					// Next kernel render loop
					sKernelBarrier.ReleaseKernelBarrier();
				}

				// Force kernels termination
				foreach (RenderKernel k in sKernels)
					k.mStopKernel = true;

				// ???
				sKernelBarrier.ReleaseKernelBarrier();
				sKernelBarrier.WaitKernelBarrier();
				sKernelBarrier.ReleaseKernelBarrier();

				foreach (RenderKernel k in sKernels)
					k.mThread.Join();

				// ! All kernel has been terminated

			} else {
				

				// Note: only one single kernel, indeed run inline on this thread

				RenderKernelThreadData threadData = new RenderKernelThreadData();
				RenderKernel renderKernel = sKernels[0];

				renderKernel.CreateRenderContext();
				threadData.Kernel = renderKernel;
				threadData.Inline = true;

				RenderKernelMain(threadData);
			}

			// Join every thread for each RenderKernel detachable service
			foreach (RenderKernelService s in sKernelServices)
				if (s.IsMultithreadModel)
					s.Join();
		}

		/// <summary>
		/// Pause every running kernels.
		/// </summary>
		public static void PauseKernels()
		{
			sPauseKernels = true;
		}

		/// <summary>
		/// Resume every running kernels.
		/// </summary>
		public static void ResumeKernels()
		{
			sPauseKernels = false;
		}

		/// <summary>
		/// Stops RenderKernel run routine.
		/// </summary>
		public static void StopKernels()
		{
			sQuitKernels = true;
		}

		/// <summary>
		/// Shared renderable for allocating shared render context.
		/// </summary>
		private static readonly RenderWindow sSharedWindow = new RenderWindow();

		/// <summary>
		/// Kernel used for sharing data.
		/// </summary>
		private static RenderContext sSharedContext;

		/// <summary>
		/// List of created kernels.
		/// </summary>
		private static readonly List<RenderKernel> sKernels = new List<RenderKernel>();

		/// <summary>
		/// Barrier for synch kernel kernels.
		/// </summary>
		private static KernelBarrier sKernelBarrier;

		/// <summary>
		/// Flag for terminating main kernel loop.
		/// </summary>
		private static bool sQuitKernels;

		/// <summary>
		/// Flag for pausing kernel threads.
		/// </summary>
		private static bool sPauseKernels;

		#endregion

		#region Render Kernel Thread

		/// <summary>
		/// 
		/// </summary>
		private class RenderKernelThreadData
		{
			/// <summary>
			/// The RenderKernel dedicated to the thread.
			/// </summary>
			public RenderKernel Kernel;

			/// <summary>
			/// Indicates whether <see cref="RenderKernelThreadData.Kernel"/> is inline with the main thread.
			/// </summary>
			public bool Inline;
		}

		/// <summary>
		/// Main loop routine for rendering.
		/// </summary>
		/// <param name="data">
		/// A <see cref="object"/> wich specify the RenderKernel instance which has executed the
		/// thread.
		/// </param>
		private static void RenderKernelMain(object data)
		{
			RenderKernelThreadData renderKernelData = (RenderKernelThreadData) data;
			RenderKernel rKernel = renderKernelData.Kernel;
			RenderContext rContext = rKernel.mRenderContext;
			List<RenderSurface> bSwapSurfaces = new List<RenderSurface>(1);

			// Make current this render context
			rContext.MakeCurrent(rKernel.mMainDeviceContext, true);

			// Initialize pipelines
			foreach (RenderPipeline rPipeline in rKernel.mRenderPipelines)
				rPipeline.InitializePipeline(rContext);

			// Determine swappable surfaces & clear flags
			foreach (RenderPipeline p in rKernel.mRenderPipelines) {
				RenderSurface rSurface = p.Surface;

				// Allocate OpenGL surfaces
				if (rSurface.GetDeviceContext() == null)
					rSurface.Create(rContext);
				// Collect swappable surfaces
				if (rSurface.Swappable == true)
					bSwapSurfaces.Add(rSurface);
			}

			// Reset RenderKernel loop flag
			rKernel.mStopKernel = false;

			// Start multithread services
			rKernel.StartMultithreadServices();

			// Kernel loop start time
			rKernel.mKernelStartTime = DateTime.Now;
			// Run the RenderKernel loop
			while (rKernel.mStopKernel == false) {
				// Execute context services
				rKernel.ExecuteContextServices();

				// Render pipelines
				foreach (RenderPipeline rPipeline in rKernel.mRenderPipelines) {
					if (rPipeline.Enabled) {
						try {
							// Reset (initial state)
							rPipeline.ResetPipeline(rContext);
							// Execute
							rPipeline.ExecutePipeline(rContext);
						} catch (Exception exception) {
							sLog.Warn("Pipeline execution has failed.", exception);
						}
					}
				}
				// Loop count
				rKernel.mKernelLoopCount++;

				// Swap surfaces
				foreach (RenderSurface s in bSwapSurfaces)
					s.SwapSurface();

				// Synch with other kernels
				if (renderKernelData.Inline == false)
					sKernelBarrier.WaitOtherKernels(false);
				else
					Application.DoEvents();
			}

			// Stop multithread services
			rKernel.StopMultithreadServices();

			if (renderKernelData.Inline == false) {
				// Synch with other kernels and terminate
				sKernelBarrier.WaitOtherKernels(true);
			}

			// Dispose the render context
			rContext.Dispose();
		}

		/// <summary>
		/// Create a dedicated render context for this RenderKernel.
		/// </summary>
		private void CreateRenderContext()
		{
			// Allocate every surface having a device context
			foreach (RenderPipeline p in mRenderPipelines) {
				IDeviceContext rDevice = p.Surface.GetDeviceContext();

				// Set main rendering surface (context created on this surface)
				if (rDevice != null) {
					mMainDeviceContext = rDevice;
					p.Surface.Create(null);
				}
			}
			if (mMainDeviceContext == null)
				throw new InvalidOperationException("kernel pipelines without window");

			// Create a RenderContext for this RenderKernel
			mRenderContext = new RenderContext(mMainDeviceContext, sSharedContext);
		}

		/// <summary>
		/// Main device context used for render context creation.
		/// </summary>
		private IDeviceContext mMainDeviceContext;

		/// <summary>
		/// RenderContext associated to this kernel.
		/// </summary>
		private RenderContext mRenderContext;

		/// <summary>
		/// Service thread. 
		/// </summary>
		private Thread mThread;

		/// <summary>
		/// Request to stop RenderKernel loop.
		/// </summary>
		private volatile bool mStopKernel;

		public float LoopsPerSecond
		{
			get
			{
				return (float)(mKernelLoopCount / (DateTime.Now - mKernelStartTime).TotalSeconds);
			}
		}

		/// <summary>
		/// The time when pipeline has been started.
		/// </summary>
		private DateTime mKernelStartTime;

		/// <summary>
		/// The numbero of loops executed by this kernel.
		/// </summary>
		private uint mKernelLoopCount;

		#endregion

		#region Render Pipelines Management

		/// <summary>
		/// Register a pipeline for rendering.
		/// </summary>
		/// <param name="rPipeline">
		/// A <see cref="RenderPipeline"/> that will be executed along with other pipelines registered to this RenderKernel.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="rPipeline"/> is null.
		/// </exception>
		public void RegisterPipeline(RenderPipeline rPipeline)
		{
			if (rPipeline == null)
				throw new ArgumentNullException("rPipeline");

			// Collect pipeline
			mRenderPipelines.Add(rPipeline);
		}

		/// <summary>
		/// Unregister a pipeline for rendering.
		/// </summary>
		/// <param name="rPipeline">
		/// A <see cref="RenderPipeline"/> that is executed along with this RenderKernel.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="rPipeline"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="rPipeline"/> is not registered on this RenderKernel.
		/// </exception>
		public void UnregisterPipeline(RenderPipeline rPipeline)
		{
			if (rPipeline == null)
				throw new ArgumentNullException("rPipeline");
			if (mRenderPipelines.Contains(rPipeline) == false)
				throw new ArgumentException("not registered", "rPipeline");

			// Forget pipeline
			mRenderPipelines.Remove(rPipeline);
		}

		/// <summary>
		/// RenderWindow instances managed by this RenderKernel. 
		/// </summary>
		private List<RenderPipeline> mRenderPipelines = new List<RenderPipeline>();

		#endregion

		#region Render Kernel Services

		/// <summary>
		/// Register an RenderKernelContextService on this RenderKernel. 
		/// </summary>
		/// <param name="service">
		/// A <see cref="RenderKernelService"/> to be executed by this RenderKernel.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="service"/> is null.
		/// </exception>
		public void RegisterService(RenderKernelService service)
		{
			if (service == null)
				throw new ArgumentNullException("service");

			// Collect service
			mKernelServices.Add(service);
		}

		/// <summary>
		/// Begin execution of context services.
		/// </summary>
		/// <remarks>
		/// Registered services will be executed before the pipelines are rendered. It's possible to issue
		/// rendering commands using the render context passed as parameter at the routine
		/// RenderKernelContextService.Begin(RenderContext). Result of rendering shall be
		/// queried when the RenderKernelContextService.End(RenderContext) routine is executed.
		/// </remarks>
		private void ExecuteContextServices()
		{
			// Execute RenderKernel context services
			foreach (RenderKernelService s in mKernelServices)
				if (s.RequiresKernelContext == true)
					s.ContextExecute(mRenderContext);
		}

		/// <summary>
		/// Start execution of services running on seperate threads.
		/// </summary>
		private void StartMultithreadServices()
		{
			foreach (RenderKernelService s in mKernelServices)
				if (s.IsMultithreadModel == true)
					s.Start();
		}

		/// <summary>
		/// Stop execution of services running on seperate threads.
		/// </summary>
		private void StopMultithreadServices()
		{
			foreach (RenderKernelService s in mKernelServices)
				if (s.IsMultithreadModel == true)
					s.Join();
		}

		/// <summary>
		/// Render kernel services (context).
		/// </summary>
		private List<RenderKernelService> mKernelServices = new List<RenderKernelService>();

		#endregion

		#region Global Kernel Services

		/// <summary>
		/// Global RenderKernel services.
		/// </summary>
		private static List<RenderKernelService> sKernelServices = new List<RenderKernelService>();

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion
	}
}
